/*
 * Albareth - an action roleplaying game.
 * Copyright (C) 2008 Jens Anuth
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *   02110-1301, USA.
 * 
 * $Id: UIElement.java 46 2008-05-15 18:09:39Z jens464 $
*/
package albareth.frontend.slick.ui;

import java.util.ArrayList;
import java.util.List;

import org.newdawn.slick.Color;
import org.newdawn.slick.Font;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Vector2f;

/**
 * Base class for all ui-elements in this game.
 * FIXME: there can be bugs when elements remove/add children
 * on mouse/key-events, because then the children-list gets manipulated
 * while being iterated.
 */
public abstract class UIElement
{
    private String _id = null;
    private UIElement _parent = null;
    protected ArrayList _children = null;
    //protected int _posX = 0;
    //protected int _posY = 0;
    protected Vector2f _pos = new Vector2f();
    protected int _paddingX = 0;
    protected int _paddingY = 0;
    protected int _componentWidth = 0;
    protected int _componentHeight = 0;
    protected boolean _opaque = false;
    protected Color _backgroundColor = Color.black;
    protected Color _foregroundColor = Color.white;
    protected Font _font = null;
    protected Image _backgroundImage = null;
    private UIElement _oldMouseOverChild = null;
    private boolean _mouseable = true;
    private boolean _visible = true;
    private UIManager _uiManager = null;
    private boolean _draggingAllowed = false;
    private boolean _droppingAllowed = false;
    private boolean _keyboardFocus;


    public UIElement()
    {
        this(0, 0, 0, 0);
    }

    /**
     * @param px x-position relative to parent.
     * @param py y-position relative to parent.
     */
    public UIElement(int px, int py)
    {
        this(px, py, 0, 0);
    }

    public UIElement(int px, int py, int width, int height)
    {
        _pos.x = px;
        _pos.y = py;
        _componentWidth = width;
        _componentHeight = height;
    }

    /**
     * Can be called to rearrange the layout of this component.
     */
    public void doLayout()
    {
    }

    public String getId()
    {
        return _id;
    }
    
    public void setId(String id)
    {
        _id = id;
    }
    
    public boolean isVisible()
    {
        return _visible;
    }

    public void setVisible(boolean visible)
    {
        _visible = visible;
    }

    
    public final void addChild(UIElement child)
    {
        child.setParent(this);
        if (child._font == null) {
            child.setFont(_font);
        }
        if (_children == null) {
            _children = new ArrayList();
        }
        _children.add(child);
    }
    
    public final boolean removeChild(UIElement child)
    {
        boolean removed = _children.remove(child);
        if (removed) {
            child.setParent(null);
        }
        return removed;
    }
    
    public void removeAllChildren()
    {
        if (_children != null) {
            _children.clear();
        }
    }


    
    public Color getBackgroundColor()
    {
        return _backgroundColor;
    }

    public void setBackgroundColor(Color backgroundColor)
    {
        _backgroundColor = backgroundColor;
    }
    
    public Image getBackgroundImage()
    {
        return _backgroundImage;
    }

    public void setBackgroundImage(Image backgroundImage)
    {
        _backgroundImage = backgroundImage;
    }
    
    public Font getFont()
    {
        if (_font == null && _parent != null) {
            return _parent.getFont();
        }
        return _font;
    }

    public void setFont(Font font)
    {
        _font = font;
    }
    
    public boolean isMouseable()
    {
        return _mouseable;
    }
    
    public void setMouseable(boolean mouseable)
    {
        _mouseable = mouseable;
    }

    public void processUpdate(int delta, GameContainer container,
            UIManager manager) throws SlickException
    {
        _uiManager  = manager;
        boolean delegate = update(delta, container);
        if (delegate && _children != null)
        {
            for (int i = 0; i < _children.size(); i++)
            {
                ((UIElement)_children.get(i)).processUpdate(
                        delta, container, manager);
            }
        }
    }

    /**
     * @return true if the update shall be delegated to the children of
     * this component.
     */
    protected boolean update(int delta, GameContainer container) throws SlickException
    {
        return true;
    }
    
    public void processRender(Graphics g, GameContainer container) throws SlickException
    {
        if (!isVisible()) {
            return;
        }
        
        int screenX = getX();
        int screenY = getY();
        if (_parent != null) {
            screenX += _parent.getX() + _parent.getPaddingX();
            screenY += _parent.getY() + _parent.getPaddingY();
        }
        
        if (_backgroundImage != null) {
            _backgroundImage.draw(screenX, screenY);
        } else if (_opaque) {
            g.setColor(_backgroundColor);
            g.fillRect(screenX, screenY, _componentWidth, _componentHeight);
        }
        
        screenX += _paddingX;
        screenY += _paddingY;
        
        Font font = getFont();
        if (font != null) {
            g.setFont(font);
        }
        
        g.setColor(_foregroundColor);
        render(g, null, screenX, screenY);
        
        renderChildren(g, container);
    }

    private void renderChildren(Graphics g, GameContainer container) throws SlickException
    {
        if (_children != null)
        {
            for (int i = 0; i < _children.size(); i++)
            {
                ((UIElement)_children.get(i)).processRender(g, container);
            }
        }
        
    }

    /**
     * @param screenX absolute screen-coordinate
     * @param screenY absolute screen-coordinate
     */
    public abstract void render(Graphics g, GameContainer container, int screenX, int screenY)
        throws SlickException;

    /**
     * @param x x-position in parent's coord-system.
     * @param y y-position in parent's coord-system.
     * @return whether the element contains the point x|y.
     */
    public final boolean contains(int x, int y)
    {
        return x > _pos.x && x < (_pos.x + _componentWidth) &&
               y > _pos.y && y < (_pos.y + _componentHeight);
    }
    
    /**
     * @param newx x-coordinate relative to this element's x-position.
     * @param newy y-coordinate relative to this element's x-position.
     */
    public boolean processMouseMoved(int newx, int newy)
    {
        int newxPadded = newx - _paddingX;
        int newyPadded = newy - _paddingY;
        
        if (_oldMouseOverChild != null &&
            !_oldMouseOverChild.contains(newxPadded, newyPadded))
        {
            _oldMouseOverChild.processMouseLeft(
                    _oldMouseOverChild.translateX(newxPadded),
                    _oldMouseOverChild.translateY(newyPadded)
            );
            _oldMouseOverChild = null;
        }
        
        boolean consumed = false;
        
        UIElement child = getChildAt(newxPadded, newyPadded);
        if (child != null && child.isMouseable())
        {
            consumed = child.processMouseMoved(
                    child.translateX(newxPadded), child.translateY(newyPadded)
            );
            _oldMouseOverChild = child;
        }
        
        if (!consumed) {
            consumed = mouseMoved(newx, newy);
        }
        
        return consumed;

    }
    
    public final boolean processMouseDrop(DraggeableItem droppedItem, int x, int y)
    {
        boolean consumed = false;
        
        int xPadded = x - _paddingX;
        int yPadded = y - _paddingY;
        
        UIElement child = getChildAt(xPadded, yPadded);
        if (child != null)
        {
            consumed = child.processMouseDrop(
                    droppedItem, child.translateX(xPadded), child.translateY(yPadded)
            );
        }
        
        if (!consumed && isDroppingAllowed() && tryDropItem(droppedItem, x, y))
        {
            droppedItem.getOwnerComponent().itemDraggedAway(
                    droppedItem
            );
            droppedItem.setOwnerComponent(this);
            consumed = true;
        }
        
        return consumed;
    }

    
    
    public void processMouseLeft(int newx, int newy)
    {
        if (_oldMouseOverChild != null)
        {
            int newxPadded = newx - _paddingX;
            int newyPadded = newy - _paddingY;
            _oldMouseOverChild.processMouseLeft(
                    _oldMouseOverChild.translateX(newxPadded),
                    _oldMouseOverChild.translateY(newyPadded)
            );
            _oldMouseOverChild = null;
        }
        
        mouseLeft(newx, newy);
    }


    /**
     * @param x x-coordinate relative to this element's x-position.
     * @param y y-coordinate relative to this element's x-position.
     */
    public boolean processMousePressed(int button, int x, int y)
    {
        boolean consumed = false;
        
        int xPadded = x - _paddingX;
        int yPadded = y - _paddingY;
        
        UIElement child = getChildAt(xPadded, yPadded);
        if (child != null)
        {
            consumed = child.processMousePressed(
                    button,
                    child.translateX(xPadded), child.translateY(yPadded)
            );
        }
        
        if (!consumed) {
            consumed = mousePressed(button, x, y);
        }
        
        return consumed;
    }

    /**
     * @param x x-coordinate relative to this element's x-position.
     * @param y y-coordinate relative to this element's x-position.
     */
    public final boolean processMouseDoubleClicked(int button, int x, int y)
    {
        boolean consumed = false;
        
        int xPadded = x - _paddingX;
        int yPadded = y - _paddingY;
        
        UIElement child = getChildAt(xPadded, yPadded);
        if (child != null)
        {
            consumed = child.processMouseDoubleClicked(
                    button,
                    child.translateX(xPadded), child.translateY(yPadded)
            );
        }
        
        if (!consumed) {
            consumed = mouseDoubleClicked(button, x, y);
        }
        
        return consumed;
    }


    /**
     * @param x x-coordinate relative to this element's x-position.
     * @param y y-coordinate relative to this element's x-position.
     */
    public boolean processMouseReleased(int button, int x, int y)
    {
        boolean consumed = false;
        
        int xPadded = x - _paddingX;
        int yPadded = y - _paddingY;
        
        UIElement child = getChildAt(xPadded, yPadded);
        if (child != null)
        {
            consumed = child.processMouseReleased(
                    button,
                    child.translateX(xPadded), child.translateY(yPadded)
            );
        }
        
        if (!consumed) {
            consumed = mouseReleased(button, x, y);
        }
        
        return consumed;
    }


    public final boolean processMouseClicked(int button, int x, int y)
    {
        boolean consumed = false;
        
        int xPadded = x - _paddingX;
        int yPadded = y - _paddingY;
        
        UIElement child = getChildAt(xPadded, yPadded);
        if (child != null)
        {
            consumed = child.processMouseClicked(
                    button,
                    child.translateX(xPadded), child.translateY(yPadded)
            );
        }
        
        if (!consumed) {
            consumed = mouseClicked(button, x, y);
        }
        
        return consumed;
    }

    /**
     * @return true if the mouse event is consumed by this component.
     */
    protected boolean mousePressed(int button, int x, int y)
    {
        return false;
    }

    /**
     * @return true if the mouse event is consumed by this component.
     */
    protected boolean mouseReleased(int button, int x, int y)
    {
        return false;
    }

    /**
     * Mouse was clicked (Pressed and released at same location).
     * @return true if the mouse event is consumed by this component.
     */
    protected boolean mouseClicked(int button, int x, int y)
    {
        return false;
    }

    /**
     * Mouse was double clicked.
     * @return true if the mouse event is consumed by this component.
     */
    protected boolean mouseDoubleClicked(int button, int x, int y)
    {
        return false;
    }


    /**
     * @return true if the mouse event is consumed by this component.
     */
    protected boolean mouseMoved(int newx, int newy)
    {
        return false;
    }

    protected void mouseLeft(int newx, int newy)
    {
    }
    
    /**
     * Drag&Drop: Asks this component for a draggeable item.
     * @return The draggeable item for this component or one of it's children
     * at pixel x|y.
     */
    public DraggeableItem getDraggeableItem(int x, int y)
    {
        UIElement child = getChildAt(x, y);
        if (child == null) {
            return null;
        } else {
            return child.getDraggeableItem(
                    child.translateX(x), child.translateY(y)
            );
        }
    }

    /**
     * Drag&Drop: Try to drop an item onto this component at x|y.
     */
    protected boolean tryDropItem(DraggeableItem droppedItem, int x, int y)
    {
        return false;
    }
    
    /**
     * Drag&Drop: An item got dragged away from this component and
     * indeed found a new owner.
     */
    protected void itemDraggedAway(DraggeableItem draggedItem)
    {
    }



    /**
     * @return true if the key event is consumed by this component.
     */
    protected boolean keyPressed(int key, char c)
    {
        return false;
    }

    /**
     * @return true if the key event is consumed by this component.
     */
    protected boolean keyReleased(int key, char c)
    {
        return false;
    }


    public final int getX()
    {
        return (int)_pos.x;
    }

    /**
     * @param x x-coord relative to parent.
     */
    public void setX(int x)
    {
        _pos.x = x;
    }

    public final int getY()
    {
        return (int)_pos.y;
    }

    /**
     * @param y y-coord relative to parent.
     */
    public void setY(int y)
    {
        _pos.y = y;
    }
    
    public void setLocation(int x, int y)
    {
        _pos.x = x;
        _pos.y = y;
    }
    
    public void setSize(int width, int height)
    {
        _componentWidth = width;
        _componentHeight = height;
    }
    
    public int getWidth()
    {
        return _componentWidth;
    }

    public void setWidth(int width)
    {
        _componentWidth = width;
    }

    public int getHeight()
    {
        return _componentHeight;
    }

    public void setHeight(int height)
    {
        _componentHeight = height;
    }
    
    public void setPadding(int paddingX, int paddingY)
    {
        _paddingX = paddingX;
        _paddingY = paddingY;
    }
    
    public int getPaddingX()
    {
        return _paddingX;
    }
    
    public int getPaddingY()
    {
        return _paddingY;
    }

    
    /**
     * Transforms x-coordinate from parent's coord-system to this component's
     * coord-system.
     */
    public final int translateX(int x)
    {
        return x - (int)_pos.x;
    }

    /**
     * Transforms y-coordinate from parent's coord-system to this component's
     * coord-system.
     */
    public final int translateY(int y)
    {
        return y - (int)_pos.y;
    }

    public final UIElement getParent()
    {
        return _parent;
    }

    public final void setParent(UIElement parent)
    {
        _parent = parent;
    }

    public final List getChildren()
    {
        return _children;
    }
    
    /**
     * @return the first child found that contains the point x|y, or null.
     */
    public UIElement getChildAt(int x, int y)
    {
        if (_children == null) {
            return null;
        }
       

        //for (int i = 0; i < _children.size(); i++)
        for (int i = _children.size()-1; i >= 0; i--)
        {
            UIElement child = (UIElement)_children.get(i);
            if (child.contains(x, y)) {
                return child;
            }
        }
        
        return null;
    }

    public boolean isOpaque()
    {
        return _opaque;
    }

    public void setOpaque(boolean opaque)
    {
        _opaque = opaque;
    }
    
    /**
     * @return the tooltip text for this component or one of it's children
     * at pixel x|y.
     */
    public String getTooltipText(int x, int y)
    {
        UIElement child = getChildAt(x, y);
        if (child == null) {
            return "";
        } else {
            return child.getTooltipText(
                    child.translateX(x), child.translateY(y)
            );
        }
    }

    public Color getForegroundColor()
    {
        return _foregroundColor;
    }

    public void setForegroundColor(Color foregroundColor)
    {
        _foregroundColor = foregroundColor;
    }

    public UIManager getUIManager()
    {
        return _uiManager;
    }

    public boolean isDraggingAllowed()
    {
        return _draggingAllowed;
    }

    public void setDraggingAllowed(boolean draggingAllowed)
    {
        _draggingAllowed = draggingAllowed;
    }


    public boolean isDroppingAllowed()
    {
        return _droppingAllowed;
    }

    public void setDroppingAllowed(boolean droppingAllowed)
    {
        _droppingAllowed = droppingAllowed;
    }

    public void setKeyboardFocus(boolean b)
    {
        _keyboardFocus = b;
    }

    public boolean hasKeyboardFocus()
    {
        return _keyboardFocus;
    }



}
